/*! 
 * jquery.event.drop - v 2.0.0 - http://jquery-ux.com/
 * Copyright (c) 2010 Michael Helgeson, Three Dub Media
 * Open Source MIT License - http://jquery-ux.com/license 
 */
// Created: 2008-06-04 
// Updated: 2010-01-08
// REQUIRES: jquery 1.3+

;(function($){ // secure $ jQuery alias

// Events: drop, dropstart, dropend

// add the jquery instance method
$.fn.drop = function( fn ){
	return this[ fn ? "bind" : "trigger" ]( "drop", fn );
	};

// DROP MANAGEMENT UTILITY
$.dropManage = function( opts ){ // return filtered drop target elements, cache their positions
	opts = opts || {};
	// safely set new options...
	drop.data = [];
	drop.multi = opts.multi === true ? drop.targets.length : 
		opts.multi === false ? 1 : !isNaN( opts.multi ) ? opts.multi : drop.multi;
	drop.filter = typeof opts.filter == "string" ? opts.filter : "*";
	drop.delay = opts.delay || drop.delay;
	drop.tolerance = opts.tolerance || null;
	drop.mode = opts.mode || drop.mode || 'intersect';
	// return the filtered set of drop targets
	return $( drop.targets ).filter( drop.filter ).each(function( i ){ 
		// locate and store the filtered drop targets
		drop.data[ i ] = drop.locate( this ); 
		drop.data[ i ].index = i;
		});
	};

// configure the special events
var drop = $.event.special.drop = $.event.special.dropstart = $.event.special.dropend = {
	
	// these are the default settings
	multi: 1, // allow multiple drop winners
	delay: 2, // async timeout delay
	mode: 'intersect', // drop tolerance mode
	
	// internal cache
	targets: [], 
	
	// configure the interactions
	setup: function( opts ){
		// determine if dropping is already initialized
		if ( $.data( this, "dropdata" ) ) return;
		// inherit default drop element settings...
		$.data( this, "dropdata", $.extend({ elem:this }, drop.defaults, opts || {}) );
		// store the drop target in internal cache
		drop.targets[ drop.targets.length ] = this;
		//drop.data[ drop.data.length ] = $.data( this, "dropdata", data );
		},
		
	// destroy the configure interaction	
	teardown: function(){ 
		var data = $.data( this, "dropdata" ),
		// query the element event structure
		events = $.data( this, "events" ) || {}, ev = 0;
		$.each(["drop","dropstart","dropend"], function( i, type ){
			if ( events[ type ] ) ev += 1;
			});
		// stop if other events still exist (or none)
		if ( !data || ev != 1 ) return;
		// remove the stored data
		$.removeData( this, "dropdata" );
		// remove from the internal cache
		drop.targets = $.grep( drop.targets, function( target ){ 
			return ( target !== data.elem ); 
			});
		},
	
	// shared event handler
	handler: function( event, dd ){ 
		// declare local refs...
		var dropstart = null, dropped;
		
		// event.dropTarget = drop.dropping || undefined; // dropped element
		
		// make sure the right data is available
		if ( !drop.data.length || !dd || !dd.drag ) return;
			
		// handle various events
		switch ( event.type ){
			
			// drag/mousemove, from $.event.special.drag
			case 'drag': // TOLERATE >>
				drop.event = event; // store the mousemove event
				if ( !drop.timer ) tolerate( dd ); // monitor drop targets
				break;
			
			// activate target, from tolerate (async)
			case 'dropstart': // DROPSTART >> 
				dd.drop = this;
				dd.drop = hijack( event, "dropstart", this, dd )!==false ? this : null;  // trigger "dropstart"
				break;
			
			// dragstop/mouseup, from $.event.special.drag
			case 'mouseup': // DROP >> DROPEND >>
				drop.timer = clearTimeout( drop.timer ); // delete timer	
				if ( drop.allowed )
					$.each( drop.data, function( i, drp ){
						if ( !drp.active ) return;
						else dd.drop = drp.elem;
						dropped = hijack( event, "drop", drp.elem, dd ); // trigger "drop"
						hijack( event, "dropend", dd.drop, dd ); // trigger "dropend"
						if ( dropped === false ) dd.drop = null;
						});
				break;
					
			// deactivate active target, from tolerate (async)
			case 'dropend': // DROPEND >> 
				hijack( event, "dropend", dd.drop, dd ); // trigger "dropend"
				break; 
						
			}
					
		},
	// returns the location positions of an element
	locate: function( elem ){ // return { L:left, R:right, T:top, B:bottom, H:height, W:width }
		var $elem = $( elem ), posi = $elem.offset(), 
		height = $elem.outerHeight(), width = $elem.outerWidth();
		return { 
			elem: elem, 
			width: width, 
			height: height,
			top: posi.top, 
			left: posi.left, 
			right: posi.left + width, 
			bottom: posi.top + height
			};
		},
	// test the location positions of an element against another OR an X,Y coord
	contains: function( target, test ){ // target { location } contains test [x,y] or { location }
		return ( ( test[0] || test.left ) >= target.left && ( test[0] || test.right ) <= target.right
			&& ( test[1] || test.top ) >= target.top && ( test[1] || test.bottom ) <= target.bottom ); 
		},
	// stored tolerance modes
	modes: { // fn scope: "$.event.special.drop" object 
		// target with mouse wins, else target with most overlap wins
		'intersect': function( event, proxy, target ){
			return this.contains( target, [ event.pageX, event.pageY ] ) ? // check cursor
				1e9 : this.modes['overlap'].apply( this, arguments ); // check overlap
			},
		// target with most overlap wins	
		'overlap': function( event, proxy, target ){
			// calculate the area of overlap...
			return Math.max( 0, Math.min( target.bottom, proxy.bottom ) - Math.max( target.top, proxy.top ) )
				* Math.max( 0, Math.min( target.right, proxy.right ) - Math.max( target.left, proxy.left ) );
			},
		// proxy is completely contained within target bounds	
		'fit': function( event, proxy, target ){
			return this.contains( target, proxy ) ? 1 : 0;
			},
		// center of the proxy is contained within target bounds	
		'middle': function( event, proxy, target ){
			return this.contains( target, [ proxy.left + proxy.width * .5, proxy.top + proxy.height * .5 ] ) ? 1 : 0;
			}
		}
	};

// set event type to custom value, and handle it
function hijack ( event, type, elem, dd ){
	event.type = type; // force the event type
	// insert old event props for compatability
	event.dropTarget = dd.drop || undefined; // dropped element
	// handle the hijacked event... clone/isolate the dd callback obj
	try { var result = $.event.handle.call( elem, event, $.extend({}, dd ) ); }
	catch ( ex ){ } // catch IE error with async event handling 
	return result===false ? false : result || event.result;
	};
	
// async, recursive tolerance execution
function tolerate( dd ){
	// declare local refs
	var i = 0, j = 0, w = 0, drp, 
	// determine the mouse coords
	xy = [ drop.event.pageX, drop.event.pageY ], 
	// determine the proxy location
	drg = drop.locate( dd.proxy ); 
	// custom or stored tolerance fn
	drop.tolerance = drop.tolerance || drop.modes[ drop.mode ]; 
	// loop each stored drop target
	do if ( drp = drop.data[i] ){ 
		// find a winner: tolerance function is defined, call it
		drp.winner = drop.tolerance ? drop.tolerance.call( drop, drop.event, drg, drp ) 
			// mouse position is always the fallback
			: drop.contains( drp, xy ) ? 1 : 0; 
		} 
	while ( ++i < drop.data.length ); // loop 
	// sort the targets by winner (dsc), then index (asc)
	drop.data.sort(function( a, b ){
		return a.winner < b.winner ? 1 : a.winner > b.winner ? -1 : a.index - b.index;
		});
	// loop through all of the targets again
	do if ( drp = drop.data[ j ] ){
		dd.drop = drp.elem;
		// winners...
		if ( drp.winner && w < drop.multi ){
			// new winner... dropstart
			if ( !drp.active ){
				drp.active = true;
				drop.event.type = "dropstart";
				drop.handler.call( drp.elem, drop.event, dd );
				}
			w += 1;	// count
			}
		// losers... dropend
		else if ( drp.active ){
			drp.active = false;
			drop.event.type = "dropend";
			drop.handler.call( drp.elem, drop.event, dd );
			}
		}
	while ( ++j < drop.data.length ); // loop 
	if ( drop.last && xy[0] == drop.last.pageX && xy[1] == drop.last.pageY ) // no movement
		delete drop.timer; // idle, don't recurse
	else drop.timer = setTimeout(function(){ tolerate( dd ); }, drop.delay ); // recurse
	drop.last = drop.event; // to compare idleness
	};

})(jQuery); // confine scope